import re
import os
import sys
import json
from collections import defaultdict

# 添加项目根目录到路径
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from config import SENSITIVE_TYPES, NER_SENSITIVE_TYPES
from utils.logger import logger

# 配置文件路径
CONFIG_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "resources", "config")
WHITELIST_CONFIG = os.path.join(CONFIG_DIR, "whitelist.json")

# 从配置文件加载白名单
def load_whitelist_config():
    """从配置文件加载白名单"""
    try:
        if os.path.exists(WHITELIST_CONFIG):
            with open(WHITELIST_CONFIG, 'r', encoding='utf-8') as f:
                config = json.load(f)
                return config.get("whitelist", [])
    except Exception as e:
        logger.info(f"加载白名单配置失败: {str(e)}")
    
    # 返回默认值
    return []

# 加载白名单
WHITELIST_LIST = load_whitelist_config()

# 转换为集合以提高查询性能
WHITELIST = set(WHITELIST_LIST)

# 简单规则 - 正则表达式模式
_PATTERNS = {
    # 身份证相关模式
    'ID_CARD': re.compile(r'\b\d{17}[\dXx]{1,4}\b'),  # 支持身份证号以XXXX结尾
    'ID_CARD_MASKED': re.compile(r'\b\d{6}[\dXx]{8}[\dXx]{3}\b'),  # 已脱敏的身份证号
    'ID_CARD_WITH_PREFIX': re.compile(r'[身份]证[号码]*[:：]\s*(\d{17}[\dXx]{1,4})'),  # 带前缀的身份证号
    'ID_CARD_WITH_PREFIX_MASKED': re.compile(r'[身份]证[号码]*[:：]\s*(\d{6}[\dXx]{8}[\dXx]{3})'),  # 带前缀的已脱敏身份证号
    
    # 电话号码相关模式
    'PHONE': re.compile(r'\b1[3-9]\d{9}\b'),  # 标准中国手机号
    'PHONE_SAMPLE': re.compile(r'\b\d{9,11}\b'),  # 匹配9-11位数字
    'TELEPHONE_WITH_PREFIX': re.compile(r'[电话传真监督][电话]*[:：]\s*(\d{3,4}-\d{7,8})'),  # 带前缀的电话号码
    'TELEPHONE_DIGITS': re.compile(r'[电话传真监督][电话]*[:：]\s*(\d{3,4}-\d{7,8})'),  # 带前缀电话的数字部分
    'TELEPHONE_GENERAL': re.compile(r'\d{3,4}-\d{7,8}'),  # 通用区号-号码格式
    'TELEPHONE_PURE_DIGITS': re.compile(r'[电话传真监督][电话]*[:：]\s*(\d{7,11})'),  # 带前缀的纯数字格式
    'TELEPHONE_SHORT': re.compile(r'[电话传真监督][电话]*[:：]\s*(\d{3,6})'),  # 带前缀的短号码
    
    # 日期相关模式
    'DATE_YEAR': re.compile(r'\d{2,4}(?=年)'),  # 年份数字
    'DATE_MONTH': re.compile(r'(?<=年)\d{1,2}(?=月)'),  # 月份数字
    'DATE_DAY': re.compile(r'(?<=月)\d{1,2}(?=日)'),  # 日期数字
    'DATE_WITH_PREFIX_DOT': re.compile(r'[签订生效开始结束][日期]*[:：]\s*(\d{4}\.\d{1,2}\.\d{1,2})'),  # 带前缀的点分隔日期
    'DATE_WITH_PREFIX_HYPHEN': re.compile(r'[签订生效开始结束][日期]*[:：]\s*(\d{4}-\d{1,2}-\d{1,2})'),  # 带前缀的横线分隔日期
    'DATE_WITH_PREFIX_SLASH': re.compile(r'[签订生效开始结束][日期]*[:：]\s*(\d{4}/\d{1,2}/\d{1,2})'),  # 带前缀的斜线分隔日期
    'DATE_DOT': re.compile(r'\b\d{4}\.\d{1,2}\.\d{1,2}\b'),  # 独立的点分隔日期
    'DATE_HYPHEN': re.compile(r'\b\d{4}-\d{1,2}-\d{1,2}\b'),  # 独立的横线分隔日期
    'DATE_SLASH': re.compile(r'\b\d{4}/\d{1,2}/\d{1,2}\b'),  # 独立的斜线分隔日期
    
    # 证件编号相关模式
    'PROPERTY_CERT1': re.compile(r'\b[^\s]{1,4}房[^\s]{0,4}证[^\s]{0,4}字第([\dXx]{1,10})号\b'),  # 房产证
    'PROPERTY_CERT2': re.compile(r'\b[^\s]{1,4}不动产权证第([\dXx]{1,10})号\b'),  # 不动产权证
    'NOTARY_NO_DIGITS': re.compile(r'(?<=[京沪津渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼][证公][字]第)([\dXx]{1,10})(?=号)'),  # 公证编号数字部分
    'NOTARY_NO_FULL': re.compile(r'[京沪津渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼][证公][字]第([\dXx]{1,10})号'),  # 完整公证编号
    'LAW_DOC_NO': re.compile(r'(?<=[京沪津渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼][法民刑行商知][字]第)([\dXx]{1,10})(?=号)'),  # 法律文书编号数字部分
    'LAW_DOC_FULL': re.compile(r'[京沪津渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼][法民刑行商知][字]第([\dXx]{1,10})号'),  # 完整法律文书编号
    
    # 地址相关模式
    'ADDRESS_BUILDING_NUM': re.compile(r'(?<=号楼)(\d+)(?=单元)'),  # 楼号数字
    'ADDRESS_UNIT_NUM': re.compile(r'(?<=单元)(\d+)(?=号)'),  # 单元号数字
    'ADDRESS_ROOM_NUM': re.compile(r'单元\d+号(\d+)(?=\s|$|，|。|；|：)'),  # 房间号数字
    'ADDRESS_BUILDING_PREFIX': re.compile(r'(\d+)(?=号楼)'),  # 楼号前缀数字
    'ADDRESS_BUILDING': re.compile(r'\b[^\s，。；：]*[小区社区][^\s，。；：]*(\d+号楼\d+单元\d+号)(?=\s|$|，|。|；|：)'),  # 完整小区地址
    'ADDRESS_ROOM': re.compile(r'\b(\d+号楼\d+单元\d+号)(?=\s|$|，|。|；|：)'),  # 楼号单元房间号
    'ADDRESS_SIMPLE': re.compile(r'\b(\d+单元\d+号)(?=\s|$|，|。|；|：)'),  # 单元房间号
    
    # 邮箱相关模式
    'EMAIL_WITH_PREFIX': re.compile(r'[电子]?邮箱[:：]\s*([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})'),  # 带前缀的邮箱
    'EMAIL_DIGITS': re.compile(r'[电子]?邮箱[:：]\s*([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})'),  # 带前缀邮箱的地址部分
    
    # 其他数字编码模式
    'POSTCODE_WITH_PREFIX': re.compile(r'邮政编码[:：]\s*(\d{6})'),  # 带前缀的邮政编码
    'POSTCODE_DIGITS': re.compile(r'邮政编码[:：]\s*(\d{6})'),  # 带前缀邮政编码的数字部分
    'MONEY_WITH_PREFIX': re.compile(r'[合同总金额人民币¥]*([\d,]+\.?\d*)\s*元'),  # 带前缀的金额
    'MONEY_DIGITS': re.compile(r'[合同总金额人民币¥]*([\d,]+\.?\d*)\s*元'),  # 带前缀金额的数字部分
    'PERCENTAGE_WITH_PREFIX': re.compile(r'[税率年利率]*\s*(\d+\.?\d*)%'),  # 带前缀的百分比
    'PERCENTAGE_DIGITS': re.compile(r'[税率年利率]*\s*(\d+\.?\d*)%'),  # 带前缀百分比的数字部分
    'CONTRACT_NO_WITH_PREFIX': re.compile(r'[合同协议编号同][编号]*[:：]\s*([A-Za-z0-9-]{8,})'),  # 带前缀的合同编号
    'CONTRACT_NO_DIGITS': re.compile(r'[合同协议编号同][编号]*[:：]\s*([A-Za-z0-9-]{8,})'),  # 带前缀合同编号的数字部分
    'BANK_ACCOUNT_WITH_PREFIX': re.compile(r'[账賬]号[:：]\s*(\d{10,19})'),  # 带前缀的银行账号
    'BANK_ACCOUNT_DIGITS': re.compile(r'[账賬]号[:：]\s*(\d{10,19})'),  # 带前缀银行账号的数字部分
    'CREDIT_CODE_WITH_PREFIX': re.compile(r'[纳税人识别号注册号统一社会信用代码][:：]\s*([A-Z0-9]{15,18})'),  # 带前缀的信用代码
    'CREDIT_CODE_DIGITS': re.compile(r'[纳税人识别号注册号统一社会信用代码][:：]\s*([A-Z0-9]{15,18})'),  # 带前缀信用代码的数字部分
    'ACCOUNT': re.compile(r'[账賬]号[:：]\s*([A-Za-z0-9]{6,})'),  # 带前缀的账号
    '测试': re.compile(r'测试\S+'),  # 测试模式
}

# 需要特殊处理的模式列表
SPECIAL_PATTERNS = {
    'PREFIX_WITH_CAPTURE': [  # 带前缀且有捕获组的模式，需要特殊提取捕获组
        'DATE_WITH_PREFIX_DOT', 'DATE_WITH_PREFIX_HYPHEN', 'DATE_WITH_PREFIX_SLASH',
        'EMAIL_DIGITS', 'TELEPHONE_DIGITS', 'TELEPHONE_PURE_DIGITS', 'TELEPHONE_SHORT',
        'POSTCODE_DIGITS', 'MONEY_DIGITS', 'PERCENTAGE_DIGITS', 'CONTRACT_NO_DIGITS',
        'BANK_ACCOUNT_DIGITS', 'CREDIT_CODE_DIGITS', 'ADDRESS_ROOM_NUM',
        'ID_CARD_WITH_PREFIX', 'ID_CARD_WITH_PREFIX_MASKED', 'TELEPHONE_WITH_PREFIX',
        'PROPERTY_CERT1', 'PROPERTY_CERT2', 'NOTARY_NO_FULL', 'LAW_DOC_FULL',
        'ADDRESS_BUILDING_NUM', 'ADDRESS_UNIT_NUM', 'ADDRESS_BUILDING_PREFIX',
        'ADDRESS_BUILDING', 'ADDRESS_ROOM', 'ADDRESS_SIMPLE',
        'EMAIL_WITH_PREFIX', 'POSTCODE_WITH_PREFIX', 'MONEY_WITH_PREFIX',
        'PERCENTAGE_WITH_PREFIX', 'CONTRACT_NO_WITH_PREFIX', 'BANK_ACCOUNT_WITH_PREFIX',
        'CREDIT_CODE_WITH_PREFIX', 'ACCOUNT'
    ],
    'DIRECT_MATCH': [  # 直接使用匹配结果的模式
        'NOTARY_NO_DIGITS', 'LAW_DOC_NO', 'DATE_DAY', 'DATE_DOT', 'DATE_HYPHEN', 
        'DATE_SLASH', 'TELEPHONE_GENERAL'
    ],
    'PREFIX_COLON': []  # 不再使用这个分类，所有带前缀的模式都移到PREFIX_WITH_CAPTURE
}

# 重新加载白名单
def reload_whitelist():
    """重新加载白名单"""
    global WHITELIST
    global WHITELIST_LIST
    
    WHITELIST_LIST = load_whitelist_config()
    WHITELIST = set(WHITELIST_LIST)


def is_in_whitelist(entity):
    """检查实体是否在白名单中"""
    # 检查是否在主白名单中
    for item in WHITELIST:
        if item in entity or entity in item:
            return True
        
    return False


def extract_captured_group(text, match, pattern_name):
    """提取捕获组内容并计算其在原文中的位置"""
    if not match.groups():
        return None
    
    captured = match.group(1)  # 第一个捕获组
    start = match.start() + match.group().find(captured)
    end = start + len(captured)
    return start, end, pattern_name


def extract_after_colon(text, match, pattern_name):
    """提取冒号后内容并计算其在原文中的位置"""
    full_match = match.group()
    prefix_end = full_match.find(':') if ':' in full_match else full_match.find('：')
    if prefix_end == -1:
        return None
        
    # 数字部分从冒号后开始
    start = match.start() + prefix_end + 1
    # 跳过可能的空格
    while start < match.end() and text[start].isspace():
        start += 1
    # 数字部分结束位置
    end = match.end()
    return start, end, pattern_name + "_DIGITS"


def simple_rule(text: str):
    """使用正则表达式识别敏感信息
    
    Args:
        text: 需要识别的文本
        
    Returns:
        list of (start, end, label): 识别到的敏感信息位置和标签
    """
    hits = []
    
    # 处理常规模式 - 跳过需要特殊处理的模式
    skip_patterns = set()
    for patterns in SPECIAL_PATTERNS.values():
        skip_patterns.update(patterns)
        
    # 常规模式处理
    for label, pat in _PATTERNS.items():
        if label in skip_patterns:
            continue
            
        for m in pat.finditer(text):
            entity = text[m.start():m.end()]
            # 应用白名单过滤
            if is_in_whitelist(entity):
                continue
            hits.append((m.start(), m.end(), label))
    
    # 处理带捕获组的模式 - 只提取捕获组内容
    for pattern_name in SPECIAL_PATTERNS['PREFIX_WITH_CAPTURE']:
        pattern = _PATTERNS.get(pattern_name)
        if not pattern:
            continue
            
        for m in pattern.finditer(text):
            result = extract_captured_group(text, m, pattern_name)
            if result:
                start, end, label = result
                entity = text[start:end]
                if not is_in_whitelist(entity):
                    hits.append((start, end, label))
    
    # 处理直接匹配的模式
    for pattern_name in SPECIAL_PATTERNS['DIRECT_MATCH']:
        pattern = _PATTERNS.get(pattern_name)
        if not pattern:
            continue
            
        for m in pattern.finditer(text):
            entity = text[m.start():m.end()]
            if not is_in_whitelist(entity):
                hits.append((m.start(), m.end(), pattern_name))
    
    return hits


def deduplicate_hits(hits):
    """
    去除重复的敏感信息检测结果
    优先保留正则检测的结果（更精确），然后保留NER模型的结果
    """
    # 使用集合来记录已检测的位置范围
    seen_positions = set()
    deduplicated_hits = []
    
    for start, end, label in hits:
        position_key = (start, end)
        
        # 如果这个位置范围还没有被检测过，则添加
        if position_key not in seen_positions:
            seen_positions.add(position_key)
            deduplicated_hits.append((start, end, label))
    
    return deduplicated_hits


def merge_adjacent_entities(hits, full_text, max_gap=1):
    """
    根据位置信息合并相邻或近距离的实体
    
    Args:
        hits: 去重后的实体列表，每项格式为 (start, end, label)
        full_text: 完整文本
        max_gap: 最大允许的字符间隔，默认为3个字符
        
    Returns:
        合并后的实体列表
    """
    if not hits:
        return []
    
    # 按照开始位置排序
    sorted_hits = sorted(hits, key=lambda x: x[0])
    
    merged = []
    current_group = [sorted_hits[0]]
    
    # 合并逻辑 - 检查位置是否相邻或距离小于阈值
    for i in range(1, len(sorted_hits)):
        prev = sorted_hits[i-1]
        curr = sorted_hits[i]
        prev_end = prev[1]
        curr_start = curr[0]
        
        # 合并条件: 位置连续(相邻或重叠) 或者距离小于阈值
        if curr_start <= prev_end or (curr_start - prev_end) <= max_gap:
            # 满足条件，合并到当前组
            current_group.append(curr)
        else:
            # 处理当前组
            if len(current_group) > 1:
                # 合并多个实体
                group_start = min(entity[0] for entity in current_group)
                group_end = max(entity[1] for entity in current_group)
                # 使用第一个实体的标签类型
                group_label = current_group[0][2]
                merged.append((group_start, group_end, group_label))
            else:
                # 单个实体，直接添加
                merged.append(current_group[0])
            
            # 开始新的组
            current_group = [curr]
    
    # 处理最后一组
    if current_group:
        if len(current_group) > 1:
            group_start = min(entity[0] for entity in current_group)
            group_end = max(entity[1] for entity in current_group)
            group_label = current_group[0][2]
            merged.append((group_start, group_end, group_label))
        else:
            merged.append(current_group[0])
    
    return merged


def detect_sensitive(chars):
    """
    处理单个字/单词级别的敏感信息识别
    使用NER模型和正则化的并集检测
    
    Args:
        chars: [(x0,y0,x1,y1,text),…] - 每个元素是单个字符的位置和内容
        
    Returns:
        list of dict: 识别到的敏感信息，带有位置和分组信息
    """
    # 1. 构建字符映射和文本
    char_map = {}  # 索引到字符信息的映射
    positions = []  # 记录每个位置对应的索引
    full_text = ""  # 拼接后的完整文本
    
    for i, (x0, y0, x1, y1, text) in enumerate(chars):
        start_pos = len(full_text)
        full_text += text
        char_map[i] = {
            'rect': (x0, y0, x1, y1),
            'text': text,
            'pos': start_pos
        }
        positions.append(i)
    
    # 2. 识别敏感信息
    all_hits = []
    
    # 2.1 正则规则检测
    logger.info(f"使用正则规则检测敏感信息...")
    regex_hits = simple_rule(full_text)
    
    # 记录正则识别结果
    if regex_hits:
        logger.info(f"正则规则检测到 {len(regex_hits)} 个敏感实体")
        for start, end, label in regex_hits:
            entity_text = full_text[start:end]
            logger.info(f"正则检测: [{label}] '{entity_text}'")
    else:
        logger.info("正则规则未检测到敏感实体")
        
    all_hits.extend(regex_hits)
    
    # 2.2 NER模型检测
    try:
        logger.info(f"使用NER模型检测敏感信息...")
        from utils.model import extract_sensitive_info
        model_results = extract_sensitive_info(full_text)
        
        # 将模型结果转换为与正则格式一致的列表
        model_hits = []
        for entity_type, entities in model_results.items():
            for entity in entities:
                model_hits.append((entity['start'], entity['end'], entity_type))
                logger.info(f"NER检测: [{entity_type}] '{full_text[entity['start']:entity['end']]}'")
        
        # 应用白名单过滤
        filtered_model_hits = filter_ner_hits(model_hits, full_text)
        if len(model_hits) != len(filtered_model_hits):
            logger.info(f"白名单过滤掉 {len(model_hits) - len(filtered_model_hits)} 个实体")
            
        all_hits.extend(filtered_model_hits)
        logger.info(f"NER模型检测到 {len(model_hits)} 个敏感实体，过滤后 {len(filtered_model_hits)} 个")
    except Exception as e:
        logger.info(f"NER模型检测失败: {e}, 仅使用正则规则")
    
    # 3. 去重和合并
    unique_hits = deduplicate_hits(all_hits)
    logger.info(f"去重后共 {len(unique_hits)} 个敏感实体")
    
    merged_hits = merge_adjacent_entities(unique_hits, full_text)
    logger.info(f"合并相邻实体后共 {len(merged_hits)} 个敏感实体")
    
    # 4. 转换为结果格式
    results = []
    for start, end, label in merged_hits:
        # 记录最终敏感实体
        entity_text = full_text[start:end]
        logger.info(f"最终敏感实体: [{label}] '{entity_text}'")
        
        # 找出需要遮挡的字符
        for pos in range(start, end):
            if pos < len(positions):
                char_idx = positions[pos]
                char_info = char_map[char_idx]
                
                # 添加分组信息
                result_item = {
                    'rect': char_info['rect'], 
                    'label': label, 
                    'text': char_info['text'],
                    'group_id': f"{start}_{end}",  # 使用start和end作为分组ID
                    'is_first': pos == start,      # 是否是分组的第一个字符
                    'is_last': pos == end - 1      # 是否是分组的最后一个字符
                }
                results.append(result_item)
    
    return results


def filter_ner_hits(hits, text):
    """过滤掉白名单中的实体，不考虑标签类型"""
    filtered = []
    for start, end, label in hits:
        entity = text[start:end]
        
        # 只要在白名单中就过滤，不考虑label
        if is_in_whitelist(entity):
            continue
            
        filtered.append((start, end, label))
        
    return filtered
